/* $Id: VBoxMPGaFence.cpp $ */
/** @file
 * VirtualBox Windows Guest Mesa3D - Gallium driver interface for WDDM kernel mode driver.
 */

/*
 * Copyright (C) 2016-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

#include "gallium/VBoxMPGaWddm.h"
#include "gallium/VBoxMPGaExt.h"
#include "gallium/Svga.h"
#include "gallium/SvgaFifo.h"
#include "gallium/SvgaHw.h"
#include "common/VBoxMPIf.h"

static GAFENCEOBJECT *g_fence_object_pool = NULL;

NTSTATUS GaFenceObjectPoolInit()
{
    g_fence_object_pool = (GAFENCEOBJECT *)GaMemAllocZero(
                          VBOXWDDM_GA_MAX_FENCE_OBJECTS * sizeof(GAFENCEOBJECT));
    if (!g_fence_object_pool) {
        LOGE("alloc fence object pool failed\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    LOGI("fence object pool %p\n", g_fence_object_pool);
    return STATUS_SUCCESS;
}

void GaFenceObjectPoolDestroy(VBOXWDDM_EXT_GA *pGaDevExt)
{
    if (g_fence_object_pool) {
        LOGI("fence object pool %p\n", g_fence_object_pool);
        GaMemFree(g_fence_object_pool);
        g_fence_object_pool = NULL;
    }
}

static GAFENCEOBJECT * GaFenceObjectPoolGet(UINT32 u32FenceHandle)
{
    if (u32FenceHandle >= VBOXWDDM_GA_MAX_FENCE_OBJECTS) {
        LOGE("invalid u32FenceHandle %u\n", u32FenceHandle);
        return NULL;
    }
    if (g_fence_object_pool[u32FenceHandle].u32FenceHandle != 0) {
        LOGE("invalid state: u32FenceHandle %u is not free\n", u32FenceHandle);
        return NULL;
    }
    memset(&g_fence_object_pool[u32FenceHandle], 0, sizeof(GAFENCEOBJECT));
    return &g_fence_object_pool[u32FenceHandle];
}

static void GaFenceObjectPoolPut(UINT32 u32FenceHandle)
{
    if (u32FenceHandle >= VBOXWDDM_GA_MAX_FENCE_OBJECTS) {
        LOGE("u32FenceHandle %u\n", u32FenceHandle);
        return;
    }
    if (g_fence_object_pool[u32FenceHandle].u32FenceHandle != u32FenceHandle) {
        LOGE("invalid state: u32FenceHangle %u is not used\n", u32FenceHandle);
        return;
    }
    g_fence_object_pool[u32FenceHandle].u32FenceHandle = 0;
}

void GaFenceObjectsDestroy(VBOXWDDM_EXT_GA *pGaDevExt,
                           PVBOXWDDM_DEVICE pDevice)
{
    gaFenceObjectsLock(pGaDevExt);

    GAFENCEOBJECT *pIter, *pNext;
    RTListForEachSafe(&pGaDevExt->fenceObjects.list, pIter, pNext, GAFENCEOBJECT, node)
    {
        if (pDevice == NULL || pIter->pDevice == pDevice)
        {
            if (RT_BOOL(pIter->fu32FenceFlags & GAFENCE_F_WAITED)) {
                KeSetEvent(&pIter->event, 0 , FALSE);
                pIter->fu32FenceFlags &= ~GAFENCE_F_WAITED;
            }

            /* Remove from list, add to the local list. */
            RTListNodeRemove(&pIter->node);
            GaIdFree(pGaDevExt->fenceObjects.au32HandleBits, sizeof(pGaDevExt->fenceObjects.au32HandleBits),
                     VBOXWDDM_GA_MAX_FENCE_OBJECTS, pIter->u32FenceHandle);
            GaFenceObjectPoolPut(pIter->u32FenceHandle);
        }
    }

    gaFenceObjectsUnlock(pGaDevExt);
}

static void gaFenceDelete(VBOXWDDM_EXT_GA *pGaDevExt, GAFENCEOBJECT *pFO)
{
    LOGI("u32FenceHandle = %d, pFO %p\n", pFO->u32FenceHandle, pFO);

    gaFenceObjectsLock(pGaDevExt);

    RTListNodeRemove(&pFO->node);
    GaIdFree(pGaDevExt->fenceObjects.au32HandleBits, sizeof(pGaDevExt->fenceObjects.au32HandleBits),
             VBOXWDDM_GA_MAX_FENCE_OBJECTS, pFO->u32FenceHandle);
    GaFenceObjectPoolPut(pFO->u32FenceHandle);

    gaFenceObjectsUnlock(pGaDevExt);
}

static void gaFenceDeleteLocked(VBOXWDDM_EXT_GA *pGaDevExt, GAFENCEOBJECT *pFO)
{
    LOGI("u32FenceHandle = %d, pFO %p\n", pFO->u32FenceHandle, pFO);

    RTListNodeRemove(&pFO->node);
    GaIdFree(pGaDevExt->fenceObjects.au32HandleBits, sizeof(pGaDevExt->fenceObjects.au32HandleBits),
             VBOXWDDM_GA_MAX_FENCE_OBJECTS, pFO->u32FenceHandle);

    GaFenceObjectPoolPut(pFO->u32FenceHandle);
}

DECLINLINE(void) gaFenceRef(GAFENCEOBJECT *pFO)
{
    ASMAtomicIncU32(&pFO->cRefs);
}

DECLINLINE(void) gaFenceUnref(VBOXWDDM_EXT_GA *pGaDevExt, GAFENCEOBJECT *pFO)
{
    UINT32 c = ASMAtomicDecU32(&pFO->cRefs);

    if (c == 0)
    {
        gaFenceDelete(pGaDevExt, pFO);
    }
}

void GaFenceUnrefLocked(VBOXWDDM_EXT_GA *pGaDevExt, GAFENCEOBJECT *pFO)
{
    UINT32 c = ASMAtomicDecU32(&pFO->cRefs);
    if (c == 0)
    {
        gaFenceDeleteLocked(pGaDevExt, pFO);
    }
}

GAFENCEOBJECT *GaFenceLookup(VBOXWDDM_EXT_GA *pGaDevExt,
                             UINT32 u32FenceHandle)
{
    /* Must be called under the fence object lock. */
    GAFENCEOBJECT *pIter;
    RTListForEach(&pGaDevExt->fenceObjects.list, pIter, GAFENCEOBJECT, node)
    {
        if (pIter->u32FenceHandle == u32FenceHandle)
        {
            gaFenceRef(pIter);
            return pIter;
        }
    }
    return NULL;
}

/*
 * Fence objects.
 */
NTSTATUS GaFenceCreate(PVBOXWDDM_EXT_GA pGaDevExt,
                       PVBOXWDDM_DEVICE pDevice,
                       UINT32 *pu32FenceHandle)
{
    GAFENCEOBJECT *pFO = NULL;
    UINT32 u32FenceHandle = 0;

    gaFenceObjectsLock(pGaDevExt);

    NTSTATUS Status = GaIdAlloc(pGaDevExt->fenceObjects.au32HandleBits, sizeof(pGaDevExt->fenceObjects.au32HandleBits),
                                VBOXWDDM_GA_MAX_FENCE_OBJECTS, &u32FenceHandle);
    if (NT_SUCCESS(Status))
    {
        pFO = GaFenceObjectPoolGet(u32FenceHandle);
        if (!pFO) {
            LOGE("GaFenceObjectPoolGet failed, u32FenceHandle=%u\n", u32FenceHandle);
            GaIdFree(pGaDevExt->fenceObjects.au32HandleBits, sizeof(pGaDevExt->fenceObjects.au32HandleBits),
                     VBOXWDDM_GA_MAX_FENCE_OBJECTS, u32FenceHandle);
            gaFenceObjectsUnlock(pGaDevExt);
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        pFO->u32FenceHandle     = u32FenceHandle;
        pFO->u32FenceState      = GAFENCE_STATE_IDLE;
        pFO->u32SeqNo           = ASMAtomicIncU32(&pGaDevExt->fenceObjects.u32SeqNoSource);
        pFO->pDevice            = pDevice;

        RTListAppend(&pGaDevExt->fenceObjects.list, &pFO->node);
        gaFenceRef(pFO);

        gaFenceObjectsUnlock(pGaDevExt);

        *pu32FenceHandle = pFO->u32FenceHandle;

        LOGI("u32FenceHandle = %d\n", pFO->u32FenceHandle);
        return STATUS_SUCCESS;
    }

    /* Failure */
    gaFenceObjectsUnlock(pGaDevExt);
    return Status;
}

NTSTATUS GaFenceQuery(PVBOXWDDM_EXT_GA pGaDevExt,
                      UINT32 u32FenceHandle,
                      UINT32 *pu32SubmittedSeqNo,
                      UINT32 *pu32ProcessedSeqNo,
                      UINT32 *pu32FenceStatus)
{
    gaFenceObjectsLock(pGaDevExt);

    GAFENCEOBJECT *pFO = GaFenceLookup(pGaDevExt, u32FenceHandle);

    gaFenceObjectsUnlock(pGaDevExt);

    LOGI("u32FenceHandle = %d, pFO %p\n", u32FenceHandle, pFO);
    if (pFO)
    {
        *pu32SubmittedSeqNo = pFO->u32SeqNo;
        switch (pFO->u32FenceState)
        {
            case GAFENCE_STATE_IDLE:      *pu32FenceStatus = GA_FENCE_STATUS_IDLE;      break;
            case GAFENCE_STATE_SUBMITTED: *pu32FenceStatus = GA_FENCE_STATUS_SUBMITTED; break;
            case GAFENCE_STATE_SIGNALED:  *pu32FenceStatus = GA_FENCE_STATUS_SIGNALED;  break;
        }

        gaFenceUnref(pGaDevExt, pFO);
    }
    else
    {
        *pu32SubmittedSeqNo = 0;
        *pu32FenceStatus = GA_FENCE_STATUS_NULL;
    }
    *pu32ProcessedSeqNo = ASMAtomicReadU32(&pGaDevExt->u32LastCompletedSeqNo);

    return STATUS_SUCCESS;
}

NTSTATUS GaFenceWait(PVBOXWDDM_EXT_GA pGaDevExt,
                     UINT32 u32FenceHandle,
                     UINT64 u64TimeoutNS)
{
    gaFenceObjectsLock(pGaDevExt);

    GAFENCEOBJECT *pFO = GaFenceLookup(pGaDevExt, u32FenceHandle);
    if (!pFO)
    {
        gaFenceObjectsUnlock(pGaDevExt);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    if (pFO->u32FenceState == GAFENCE_STATE_SIGNALED)
    {
        /* Already signaled. */
        gaFenceObjectsUnlock(pGaDevExt);
        gaFenceUnref(pGaDevExt, pFO);
        return STATUS_SUCCESS;
    }

    /* Wait */
    if (!RT_BOOL(pFO->fu32FenceFlags & GAFENCE_F_WAITED))
    {
        KeInitializeEvent(&pFO->event, NotificationEvent, FALSE);
        pFO->fu32FenceFlags |= GAFENCE_F_WAITED;
    }

    gaFenceObjectsUnlock(pGaDevExt);

    LOGI("u32FenceHandle = %d, pFO %p\n", u32FenceHandle, pFO);

    LARGE_INTEGER Timeout;
    Timeout.QuadPart = u64TimeoutNS;
    Timeout.QuadPart *= -1LL; /* to relative 100-nanoseconds units. */
    NTSTATUS Status = KeWaitForSingleObject(&pFO->event, UserRequest, KernelMode, TRUE, &Timeout);

    gaFenceUnref(pGaDevExt, pFO);

    return Status;
}

NTSTATUS GaFenceDelete(PVBOXWDDM_EXT_GA pGaDevExt,
                       UINT32 u32FenceHandle)
{
    gaFenceObjectsLock(pGaDevExt);

    GAFENCEOBJECT *pFO = GaFenceLookup(pGaDevExt, u32FenceHandle);
     if (!pFO)
    {
        gaFenceObjectsUnlock(pGaDevExt);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    if (RT_BOOL(pFO->fu32FenceFlags & GAFENCE_F_DELETED))
    {
        /* Undo GaFenceLookup ref. */
        GaFenceUnrefLocked(pGaDevExt, pFO);

        gaFenceObjectsUnlock(pGaDevExt);
        return STATUS_INVALID_PARAMETER;
    }

    pFO->fu32FenceFlags |= GAFENCE_F_DELETED;

    if (RT_BOOL(pFO->fu32FenceFlags & GAFENCE_F_WAITED))
    {
        KeSetEvent(&pFO->event, 0, FALSE);
        pFO->fu32FenceFlags &= ~GAFENCE_F_WAITED;
    }

    /* Undo GaFenceLookup ref. */
    GaFenceUnrefLocked(pGaDevExt, pFO);

    /* Undo the GaFenceCreate ref. */
    GaFenceUnrefLocked(pGaDevExt, pFO);

    gaFenceObjectsUnlock(pGaDevExt);

    LOGI("u32FenceHandle = %d, pFO %p\n", u32FenceHandle, pFO);

    return STATUS_SUCCESS;
}
